<?php


/**
 * sql语句处理类,该类封装了基础的sql处理方法.
 *
 * 实现了基础的,数据绑定,参数绑定以及基础查询接口.
 * 通过调用'WindConnection'的'createStatement()'方法,可以获得一个statement对象.
 *
 * @author Qiong Wu <papa0924@gmail.com> 2011-9-23
 * @copyright ©2003-2103 phpwind.com
 * @license http://www.windframework.com
 *
 * @version $Id: WindSqlStatement.php 3904 2013-01-08 07:01:26Z yishuo $
 */
class WindSqlStatement
{
    /**
     * @var WindConnection
     */
    private $_connection;
    /**
     * @var PDOStatement
     */
    private $_statement = null;
    /**
     * @var string
     */
    private $_queryString;
    /**
     * PDO类型映射.
     *
     * @var array
     */
    private $_typeMap = array(
        'boolean' => PDO::PARAM_BOOL,
        'integer' => PDO::PARAM_INT,
        'string'  => PDO::PARAM_STR,
        'NULL'    => PDO::PARAM_NULL, );
    /**
     * @var array
     */
    private $_columns = array();
    /**
     * @var array
     */
    private $_param = array();

    /**
     * @param WindConnection $connection WindConnection对象
     * @param string         $query      预定义语句
     */
    public function __construct($connection = null, $query = '')
    {
        $connection && $this->_connection = $connection;
        $query && $this->setQueryString($query);
    }

    /**
     * 参数绑定.
     *
     * @param mixed $parameter     预定义语句的待绑定的位置
     * @param mixed &$variable     绑定的值
     * @param int   $dataType      值的类型(PDO::PARAM_STR/PDO::PARAM_INT...)
     * @param int   $length        绑定值的长度
     * @param mixed $driverOptions
     *
     * @throws WindDbException
     *
     * @return WindSqlStatement
     *
     * @see PDOStatement::bindParam()
     */
    public function bindParam($parameter, &$variable, $dataType = null, $length = null, $driverOptions = null)
    {
        try {
            if ($dataType === null) {
                $dataType = $this->_getPdoDataType($variable);
            }
            if ($length === null) {
                $this->getStatement()->bindParam($parameter, $variable, $dataType);
            } else {
                $this->getStatement()->bindParam($parameter, $variable, $dataType, $length, $driverOptions);
            }
            $this->_param[$parameter] = $variable;

            return $this;
        } catch (PDOException $e) {
            throw new WindDbException('[db.WindSqlStatement.bindParam] '.$e->getMessage());
        }
    }

    /**
     * 批量绑定变量.
     *
     * 如果是一维数组，则使用key=>value的形式，key代表变量位置，value代表替换的值，而替换值需要的类型则通过该值的类型来判断---不准确
     * 如果是一个二维数组，则允许，key=>array(0=>value, 1=>data_type, 2=>length, 3=>driver_options)的方式来传递变量。
     *
     * @param array $parameters
     *
     * @throws WindDbException
     *
     * @return WindSqlStatement
     *
     * @see PDOStatement::bindParam()
     */
    public function bindParams(&$parameters)
    {
        if (!is_array($parameters)) {
            throw new WindDbException(
            '[db.WindSqlStatement.bindParams] Error unexpected paraments type '.gettype($parameters));
        }

        $keied = (array_keys($parameters) !== range(0, count($parameters) - 1));
        foreach ($parameters as $key => $value) {
            $_key = $keied ? $key : $key + 1;
            if (is_array($value)) {
                $dataType = isset($value[1]) ? $value[1] : $this->_getPdoDataType($value[0]);
                $length = isset($value[2]) ? $value[2] : null;
                $driverOptions = isset($value[3]) ? $value[3] : null;
                $this->bindParam($_key, $parameters[$key][0], $dataType, $length, $driverOptions);
            } else {
                $this->bindParam($_key, $parameters[$key], $this->_getPdoDataType($value));
            }
        }

        return $this;
    }

    /**
     * 参数绑定.
     *
     * @param string $parameter 预定义语句的待绑定的位置
     * @param string $value     绑定的值
     * @param int    $data_type 值的类型
     *
     * @throws WindDbException
     *
     * @return WindSqlStatement
     *
     * @see PDOStatement::bindValue()
     */
    public function bindValue($parameter, $value, $data_type = null)
    {
        try {
            if ($data_type === null) {
                $data_type = $this->_getPdoDataType($value);
            }
            $this->getStatement()->bindValue($parameter, $value, $data_type);
            $this->_param[$parameter] = $value;

            return $this;
        } catch (PDOException $e) {
            throw new WindDbException('[db.WindSqlStatement.bindValue] '.$e->getMessage());
        }
    }

    /**
     * 调用bindValue的批量绑定参数.
     *
     * @param array $values 待绑定的参数值
     *
     * @throws WindDbException
     *
     * @return WindSqlStatement
     *
     * @see PDOStatement::bindValue()
     */
    public function bindValues($values)
    {
        if (!is_array($values)) {
            throw new WindDbException(
            '[db.WindSqlStatement.bindValues] Error unexpected paraments type \''.gettype($values).'\'');
        }

        $keied = (array_keys($values) !== range(0, count($values) - 1));
        foreach ($values as $key => $value) {
            if (!$keied) {
                $key = $key + 1;
            }
            $this->bindValue($key, $value, $this->_getPdoDataType($value));
        }

        return $this;
    }

    /**
     * 绑定输出结果集的列到PHP变量.
     *
     * @param mixed $column     需要被绑定的字段列表，可以是字段名，也可以是字段的对应的下标
     * @param mixed &$param     需要被绑定的php变量
     * @param int   $type       参数的数据类型 PDO::PARAM_*
     * @param int   $maxlen     A hint for pre-allocation.
     * @param mixed $driverdata Optional parameter(s) for the driver.
     *
     * @throws WindDbException
     *
     * @return WindSqlStatement
     *
     * @see PDOStatement::bindColumn()
     */
    public function bindColumn($column, &$param = '', $type = null, $maxlen = null, $driverdata = null)
    {
        try {
            if ($type == null) {
                $type = $this->_getPdoDataType($param);
            }
            if ($type == null) {
                $this->getStatement()->bindColumn($column, $param);
            } elseif ($maxlen == null) {
                $this->getStatement()->bindColumn($column, $param, $type);
            } else {
                $this->getStatement()->bindColumn($column, $param, $type, $maxlen, $driverdata);
            }
            $this->_columns[$column] = &$param;

            return $this;
        } catch (PDOException $e) {
            throw new WindDbException('[db.WindSqlStatement.bindColumn] '.$e->getMessage());
        }
    }

    /**
     * 批量绑定输出结果集的列到PHP变量.
     *
     * @param array $columns 待绑定的列
     * @param array &$param  需要绑定的php变量
     *
     * @see PDOStatement::bindColumn()
     *
     * @return WindSqlStatement
     */
    public function bindColumns($columns, &$param = array())
    {
        $int = 0;
        foreach ($columns as $value) {
            $this->bindColumn($value, $param[$int++]);
        }

        return $this;
    }

    /**
     * 绑定参数，执行SQL语句，并返回更新影响行数.
     *
     * @param array $params   预定义语句中需要绑定的参数
     * @param bool  $rowCount 是否返回影响行数
     *
     * @throws WindDbException
     *
     * @return int|bool
     */
    public function update($params = array(), $rowCount = false)
    {
        return $this->execute($params, $rowCount);
    }

    /**
     * 绑定参数，执行SQL语句，并返回查询结果.
     *
     * @param array $params    预定义语句中需要绑定的参数
     * @param int   $fetchMode 获得结果集的模式PDO::FETCH_BOTH/PDO::FETCH_ASSOC/PDO::FETCH_NUM
     * @param int   $fetchType 设置结果集的读取方式，PDO::FETCH_ORI_NEXT/PDO::FETCH_ORI_PRE，注意要使用该属性，必须通过setAttribute设置PDO::ATTR_CURSOR=PDO::CURSOR_SCROLL
     *
     * @return WindResultSet
     */
    public function query($params = array(), $fetchMode = 0, $fetchType = 0)
    {
        $this->execute($params, false);

        return new WindResultSet($this, $fetchMode, $fetchType);
    }

    /**
     * 绑定参数，执行SQL语句，并返回查询结果.
     *
     * @param array  $params    预定义语句中需要绑定的参数
     * @param string $index     返回的数组的下标对应的字段
     * @param int    $fetchMode 获得结果集的模式PDO::FETCH_BOTH/PDO::FETCH_ASSOC/PDO::FETCH_NUM
     * @param int    $fetchType 设置结果集的读取方式，PDO::FETCH_ORI_NEXT/PDO::FETCH_ORI_PRE，注意要使用该属性，必须通过setAttribute设置PDO::ATTR_CURSOR=PDO::CURSOR_SCROLL
     *
     * @return array 返回处理后的结果
     */
    public function queryAll($params = array(), $index = '', $fetchMode = 0, $fetchType = 0)
    {
        $this->execute($params, false);
        $rs = new WindResultSet($this, $fetchMode, $fetchType);

        return $rs->fetchAll($index);
    }

    /**
     * 绑定参数，执行SQL语句，并返回查询到的结果集中某一个列的值
     *
     * @param array $params 预定义语句中需要绑定的参数
     * @param int   $column 列的下标，默认为0即第一列
     *
     * @return string
     */
    public function getValue($params = array(), $column = 0)
    {
        $this->execute($params, false);
        $rs = new WindResultSet($this, PDO::FETCH_NUM, 0);

        return $rs->fetchColumn($column);
    }

    /**
     * 绑定参数，执行SQL语句，并返回一行查询结果.
     *
     * @param array $params    预定义语句中需要绑定的参数
     * @param int   $fetchMode 获得结果集的模式PDO::FETCH_BOTH/PDO::FETCH_ASSOC/PDO::FETCH_NUM
     * @param int   $fetchType 设置结果集的读取方式，PDO::FETCH_ORI_NEXT/PDO::FETCH_ORI_PRE，注意要使用该属性，必须通过setAttribute设置PDO::ATTR_CURSOR=PDO::CURSOR_SCROLL
     *
     * @return array
     */
    public function getOne($params = array(), $fetchMode = 0, $fetchType = 0)
    {
        $this->execute($params, false);
        $rs = new WindResultSet($this, $fetchMode, $fetchType);

        return $rs->fetch();
    }

    /* (non-PHPdoc)
     * @see WindConnection::lastInsterId()
     */
    public function lastInsertId($name = '')
    {
        return $this->getConnection()->lastInsertId($name);
    }

    /**
     * 绑定参数，执行SQL语句，并返回影响行数.
     *
     * @param array $params   --  绑定的参数和bindValues的参数一样
     * @param bool  $rowCount 是否返回受印象行数
     *
     * @return rowCount
     */
    public function execute($params = array(), $rowCount = true)
    {
        try {
            $this->bindValues($params);
            $this->getStatement()->execute();
            $_result = $rowCount ? $this->getStatement()->rowCount() : true;

            return $_result;
        } catch (PDOException $e) {
            throw new WindDbException('[db.WindSqlStatement.execute] '.$e->getMessage()."\r\nSQL:".$this->getQueryString());
        }
    }

    /**
     * @param string $sql
     */
    public function setQueryString($sql)
    {
        try {
            $this->_queryString = $sql;
            $this->_statement = $this->getConnection()->getDbHandle()->prepare($sql);
        } catch (PDOException $e) {
            throw new WindDbException('[db.WindSqlStatement.setQueryString] Initialization WindSqlStatement failed.'.$e->getMessage());
        }
    }

    /**
     * 获得查询的预定义语句.
     *
     * @return string
     */
    public function getQueryString()
    {
        return $this->_queryString;
    }

    /**
     * @param WindConnection $connection
     */
    public function setConnection($connection)
    {
        $this->_connection = $connection;
    }

    /**
     * 获得PDO链接对象
     *
     * @return WindConnection
     */
    public function getConnection()
    {
        return $this->_connection;
    }

    /**
     * 获得PDOStatement对象
     *
     * @return PDOStatement
     */
    public function getStatement()
    {
        return $this->_statement;
    }

    /**
     * 获取参数绑定列表.
     *
     * @return array
     */
    public function getParams()
    {
        return $this->_param;
    }

    /**
     * 获得需要绑定的结果输出的列值
     *
     * @return array
     */
    public function getColumns()
    {
        return $this->_columns;
    }

    /**
     * 获得绑定参数的类型.
     *
     * @param string $variable
     *
     * @return int
     */
    private function _getPdoDataType($variable)
    {
        return isset($this->_typeMap[gettype($variable)]) ? $this->_typeMap[gettype($variable)] : PDO::PARAM_STR;
    }
}
